﻿using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using System;

namespace Red.Core.Network.PacketNetwork
{
    public struct PacketHeader
    {
        public uint Magic;
        public uint ContentSize;
        public uint ContentCrc;
        public uint HeaderCrc;
    };

    internal class PacketNetworkParser
    {
        private const uint PacketMagicNumberOneByte = 0x40404040;
        private readonly int HeaderSize = Marshal.SizeOf<PacketHeader>();

        enum EParsingState
        {
            ParsingHeader,
            ParsingContent,
        }

        public PacketNetworkParser()
        {
            _streamCurrentOffset = 0;
            _buffer.SetLength( 0 );
        }

        public void AppendAndParse( byte[] buffer, int size, ref List<MemoryStream> packets )
        {
            ProcessParsingState( buffer, size, ref packets );
        }

        private void ProcessParsingState( byte[] buffer, int size, ref List<MemoryStream> packets )
        {
            //Debug.WriteLine(string.Format("[PacketNetwork] Parsing new message, pos {0} data {1}, buffer {2}", _streamCurrentOffset, size, _buffer.Length));
            _buffer.Seek( 0, SeekOrigin.End );
            _buffer.Write( buffer, 0, size );

            bool keepParsing = true;
            while ( keepParsing )
            {
                switch ( _parsingState )
                {
                    case EParsingState.ParsingHeader:
                        {
                            //Debug.WriteLine("[PacketNetwork] Trying Reading header");
                            if ( TryToParseHeader( buffer, size ) )
                            {
                                _parsingState = EParsingState.ParsingContent;
                            }
                            else
                            {
                                //Debug.WriteLine("[PacketNetwork] WRONG HEADER");
                                keepParsing = false;
                            }

                            break;
                        }

                    case EParsingState.ParsingContent:
                        {
                            //Debug.WriteLine("[PacketNetwork] Trying to parse content");
                            if ( TryToParseContent( buffer, size, ref packets ) )
                            {
                                _parsingState = EParsingState.ParsingHeader;
                            }
                            else
                            {
                                //Debug.WriteLine("[PacketNetwork] WRONG CONTENT");
                                keepParsing = false;
                            }

                            break;
                        }
                }

                if ( _streamCurrentOffset == _buffer.Length )
                {
                    //Debug.WriteLine("[PacketNetwork] Reached end of buffer, resetting");
                    _buffer.SetLength( 0 );
                    _streamCurrentOffset = 0;
                    keepParsing = false;
                }
            }
        }

        private bool TryToParseHeader( byte[] buffer, int size )
        {
            if ( ( _buffer.Length - _streamCurrentOffset ) >= HeaderSize )
            {
                //Debug.WriteLine("[PacketNetwork] Reading header");

                _buffer.Seek( _streamCurrentOffset, SeekOrigin.Begin );
                BinaryReader reader = new BinaryReader( _buffer );
                _currentHeader.Magic = reader.ReadUInt32();

                if ( _currentHeader.Magic != PacketMagicNumberOneByte )
                {
                    //Debug.WriteLine( "[PacketNetwork] Invalid header magic." );
                    return true;
                }

                _currentHeader.ContentSize = reader.ReadUInt32();
                _currentHeader.ContentCrc = reader.ReadUInt32();
                _currentHeader.HeaderCrc = reader.ReadUInt32();

                //Debug.WriteLine(string.Format("[PacketNetwork] Header content size: {0} crc: {1}", _currentHeader.ContentSize, _currentHeader.ContentCrc));

                _streamCurrentOffset += HeaderSize;

                uint headerCrc = CalculateHeaderCrc32();

                if ( _currentHeader.HeaderCrc == headerCrc )
                {
                    //Debug.WriteLine("[PacketNetwork] Valid header magic");
                }
                else
                {
                    //Debug.WriteLine( "[PacketNetwork] Invalid header CRC." );
                }
                return true;
            }

            return false;
        }

        private bool TryToParseContent( byte[] buffer, int size, ref List<MemoryStream> packets )
        {
            if ( ( _buffer.Length - _streamCurrentOffset ) >= _currentHeader.ContentSize )
            {
                //Debug.WriteLine("[PacketNetwork] Reading content");
                _buffer.Seek( _streamCurrentOffset, SeekOrigin.Begin );

                byte[] bufferArray = new byte[ _currentHeader.ContentSize ];
                _buffer.Read( bufferArray, 0, (int)_currentHeader.ContentSize );

                _streamCurrentOffset += _currentHeader.ContentSize;

                uint contentCrc = PacketCreator.CalculateCrc32( bufferArray );

                if ( _currentHeader.ContentCrc == contentCrc )
                {
                    //Debug.WriteLine("[PacketNetwork] Valid content CRC");

                    MemoryStream packet = new MemoryStream( bufferArray );
                    packets.Add( packet );
                }
                else
                {
                    //Debug.WriteLine( "[PacketNetwork] Invalid content CRC." );
                }

                return true;
            }

            return false;
        }

        private uint CalculateHeaderCrc32()
        {
            uint[] headerBuffer = new uint[3];
            headerBuffer[0] = _currentHeader.Magic;
            headerBuffer[1] = _currentHeader.ContentSize;
            headerBuffer[2] = _currentHeader.ContentCrc;

            byte[] bufferArray = new byte[ headerBuffer.Length * sizeof(int)];
            Buffer.BlockCopy(headerBuffer, 0, bufferArray, 0, bufferArray.Length);

            return PacketCreator.CalculateCrc32( bufferArray );
        }

        private long _streamCurrentOffset = 0;
        private MemoryStream _buffer = new MemoryStream();
        private EParsingState _parsingState = EParsingState.ParsingHeader;
        private PacketHeader _currentHeader = new PacketHeader();
    }
}
